/*
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.util;

import java.io.Serializable;
import java.util.BitSet;

import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.data.NeighborManager;

/**
 * The SudokuValidator offers a high speed solution to check the validity of a Sudoku puzzle.
 * @author Emanuel Moecklin
 */
public class SudokuValidator implements Serializable {

	private static final long serialVersionUID = 8648431148361845770L;

	// the 81 Sudoku values (1..9, 0 is an empty cell)
	private byte[] values;

	// the 81 candidates (bits 1..9 used)
	private BitSet[] candidates;

	private int[][] neighbors;

	/**
	 * Creates a new and initialized instance of the SudokuValidator.
	 * The validator is populated from an explainer grid.
	 * Important: use this SudokuValidator for digit based techniques only because it recomputes
	 * them automatically and doesn't consider the pencil marks on the current board.
	 * @param grid
	 */
	public SudokuValidator(Grid grid) {
		values = new byte[81];
		candidates = new BitSet[81];
		neighbors = NeighborManager.getNeighbors(grid);

    	// initialize grid
		for (int y=0; y<9; y++) {
        	for (int x=0; x<9; x++) {
        		Cell cell = grid.getCell(x, y);
        		if (!cell.isEmpty()) setValue(x, y, cell.getValue());
        	}
    	}
	}
	
	/**
	 * Returns True if the cell is empty (no set value, only candidates), False otherwise
	 */
	public boolean isEmpty(int pos) {
		return values[pos]==0;
	}
	
	/**
	 * Sets the value at a certain position
	 * @param x x-coordinate of the value to set (0..8)
	 * @param y y-coordinate of the value to set (0..8)
	 * @param value the value to set (1..9)
	 */
	public void setValue(int x, int y, int value) {
		int pos = getPos(x, y);
		values[pos] = (byte) value;
		// remove all candidates from this cell (except the value itself)
		BitSet cand = getCandidates(pos);
		cand.clear();
		cand.set(value);
		// remove the candidate in neighboring cells (row, column, block)
		for (int m:neighbors[pos]) {
			getCandidates(m).clear(value);
		}
	}

	/**
	 * Checks validity of the Sudoku if value would be set to x/y.
	 * Note: can be used to check candidates too!.
	 * @param x x-coordinate of the value to check (0..8)
	 * @param y y-coordinate of the value to check (0..8)
	 * @param value the value to check (1..9)
	 * @return True if the Sudoku is still valid, False otherwise
	 */
	public boolean checkValue(int x, int y, int value) {
		return getCandidates(getPos(x, y)).get(value);
	}

	/**
	 * Return the candidates as BitSet
	 * @param x x-coordinate of the value to check (0..8)
	 * @param y y-coordinate of the value to check (0..8)
	 * @return The candidates as BitSet.
	 */
	public BitSet getCandidates(int x, int y) {
		return getCandidates(getPos(x, y));
	}

	/**
	 * Initialize candidate BitSet if it's still Null
	 */
	public BitSet getCandidates(int pos) {
		if (candidates[pos]==null) {
			candidates[pos] = new BitSet();
			candidates[pos].set(1, 10);
		}
		return candidates[pos];
	}
	
	private int getPos(int x, int y) {
		return y*9 + x;
	}

	/**
     * Get a string representation of this grid.
     * For debugging purpose only.
     */
    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        for (int y=0; y<9; y++) {
            for (int x=0; x<9; x++) {
        		int pos = y*9+x;
                int value = values[pos];
                result.append((value>=1&&value<=9) ? ""+value : "."); //$NON-NLS-1$ //$NON-NLS-2$
            }
            result.append('\n');
        }
        return result.toString();
    }
}